// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"fmt"
	"reflect"
)

// 来自可信来源的内容字符串。
type (
	// CSS封装了已知的安全内容，这些内容与以下任何内容匹配：
	// 1。CSS3样式表产品，例如'p{color:purple}'。
	// 2。CSS3规则生成，例如`a[href=~”https:].foo#bar`。
	// 3。CSS3声明产品，如`颜色：红色；边距：2px`。
	// 4。CSS3值生产，例如“rgba（0，0，255，127）`。
	// 请参阅https:
	// https:
	// 
	// 使用此类型会带来安全风险：
	// 封装的内容应该来自可信的源
	// 因为它将被逐字包含在模板输出中。
	CSS string

	// HTML封装了已知的安全HTML文档片段。
	// 它不应用于来自第三方的HTML，或带有
	// 未关闭的标记或注释的HTML。声音HTML消毒剂
	// 和此包转义的模板的输出可以与HTML一起使用。
	// 
	// 使用此类型会带来安全风险：
	// 封装的内容应该来自可信的源
	// 因为它将被逐字包含在模板输出中。
	HTML string

	// HTMLAttr封装来自可信源的HTML属性，例如，`dir=“ltr”`。
	// 
	// 使用这种类型会带来安全风险：
	// 封装的内容应该来自可信的源
	// 因为它将被逐字包含在模板输出中。
	HTMLAttr string

	// JS封装了一个已知的安全ECMAScript 5表达式，例如，
	// `（x+y*z（））`。
	// 模板作者负责确保类型化表达式
	// 不会破坏预期的优先级，并且在传递类似
	// /“{foo:bar（）}\n['foo']（）”的表达式时，不会出现语句/表达式歧义，这既是一个有效的表达式，也是一个
	// 具有完全不同含义的有效程序。
	// 
	// 使用这种类型会带来安全风险：
	// 封装的内容应该来自可信的源
	// 因为它将被逐字包含在模板输出中。
	// 
	// 使用JS包含有效但不受信任的JSON是不安全的。
	// 一个安全的替代方法是使用JSON.Unmarshal解析JSON，然后
	// 将结果对象传递到模板中，在模板中，当在JavaScript上下文中呈现时，它将被
	// 转换为经过消毒的JSON。
	JS string

	// JSStr封装了一系列字符，这些字符将嵌入JavaScript表达式中的引号之间。
	// 字符串必须匹配一系列StringCharacters:
	// StringCharacter:：SourceCharacter，但不能``或行终止符
	// /| EscapeSequence 
	// 请注意，不允许行连续。
	// JSStr（“foo\\nbar”）可以，但JSStr（“foo\\\nbar”）不行。
	// 
	// 使用此类型会带来安全风险：
	// 封装的内容应该来自可信的源
	// 因为它将被逐字包含在模板输出中。
	JSStr string

	// URL封装了已知的安全URL或URL子字符串（请参见RFC 3986）。
	// javascript:checkthatformnotededebeforeavingpage（）`
	// 之类的URL应该放在页面中，但默认情况下，动态
	// `javascript:` URL会被过滤掉，因为它们是经常被利用的
	// 注入向量。
	// 
	// 使用此类型会带来安全风险：
	// 封装的内容应该来自可信的源
	// 因为它将被逐字包含在模板输出中。
	URL string

	// Srcset封装了已知的安全Srcset属性
	// （请参阅https:
	// 
	// 使用此类型会带来安全风险：
	// 封装的内容应来自受信任的来源，因为它将被逐字包含在模板输出中。
	Srcset string
)

type contentType uint8

const (
	contentTypePlain contentType = iota
	contentTypeCSS
	contentTypeHTML
	contentTypeHTMLAttr
	contentTypeJS
	contentTypeJSStr
	contentTypeURL
	contentTypeSrcset
	// attr.go中使用了contenttypesafety，请查找影响
	// 嵌入式内容和网络消息已形成、审查、
	// 或解释；或者网络消息携带哪些凭据。
	contentTypeUnsafe
)

// 根据需要多次取消引用
// 以到达基类型（或nil）后，间接返回值。
func indirect(a interface{}) interface{} {
	if a == nil {
		return nil
	}
	if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
		// 如果不是指针，请避免创建reflect.Value。
		return a
	}
	v := reflect.ValueOf(a)
	for v.Kind() == reflect.Ptr && !v.IsNil() {
		v = v.Elem()
	}
	return v.Interface()
}

var (
	errorType       = reflect.TypeOf((*error)(nil)).Elem()
	fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
)

// IndirectToString
// 以达到基类型（或零）或fmt的实现。Stringer 
// 或错误，
func indirectToStringerOrError(a interface{}) interface{} {
	if a == nil {
		return nil
	}
	v := reflect.ValueOf(a)
	for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
		v = v.Elem()
	}
	return v.Interface()
}

// stringify将其参数转换为字符串和内容类型。
// 所有指针都被取消引用，就像在文本/模板包中一样。
func stringify(args ...interface{}) (string, contentType) {
	if len(args) == 1 {
		switch s := indirect(args[0]).(type) {
		case string:
			return s, contentTypePlain
		case CSS:
			return string(s), contentTypeCSS
		case HTML:
			return string(s), contentTypeHTML
		case HTMLAttr:
			return string(s), contentTypeHTMLAttr
		case JS:
			return string(s), contentTypeJS
		case JSStr:
			return string(s), contentTypeJSStr
		case URL:
			return string(s), contentTypeURL
		case Srcset:
			return string(s), contentTypeSrcset
		}
	}
	i := 0
	for _, arg := range args {
		// 为了向后兼容，我们跳过了非类型的nil参数。
		// 如果没有这个，它们将被输出为<nil>，转义。
		// 见第25875期。
		if arg == nil {
			continue
		}

		args[i] = indirectToStringerOrError(arg)
		i++
	}
	return fmt.Sprint(args[:i]...), contentTypePlain
}
